#region
/* Adoor.NET - Object/Relational mapping framework leveraging ADO.NET`s powerful disconnected mode.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *  
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *  
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */ 
#endregion

//#define EscapeNames

using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using Adoor.Expression;
using Adoor.Expression.AST;

namespace Adoor.Relational.SqlDriver
{
	// see http://www.csis.gvsu.edu/GeneralInfo/Oracle/index.htm
	public class PlSql8Driver: CommonSqlDriver
	{
		public PlSql8Driver(int paramCount) : base(paramCount)
		{
		}

		public override string GetSelectCommand(DataTable table, ICollection<DataColumn> dataColumns)
		{
			return base.GetSelectCommand(table, dataColumns);
		}

		protected override void Evaluate(SqlMode mode, RelationalQuery query, ICollection<SelectExpression> columns, SortExpression[] sort, Int64 top, string filter
			, GroupByExpression[] groupBy, HavingExpression having, out string[] results)
		{
			base.Evaluate(mode, query, columns, sort, top, filter, groupBy, having, out results);
			for(int i = 0; i<results.Length; i++)
			{
				string result = results[i];
				result = result.Replace(" AS ", " ");
#if EscapeNames
			result = result.Replace("[", "\"");  // TODO: check if still needed
			result = result.Replace("]", "\"");  // TODO: check if still needed
#else
				result = result.Replace("[", "");  // TODO: check if still needed
				result = result.Replace("]", "");  // TODO: check if still needed
#endif
				results[i] = result;
			}			
		}
		
		protected override CommonSqlDriver Clone()
		{
			return new PlSql8Driver(this.paramCount);
		}

		public static string EscapePlSql(string name)
		{
#if EscapeNames
			return "\"" + name + "\"";
#else
			return name;
#endif
		}

		protected override string Escape(string name)
		{
			return EscapePlSql(name);
		}

		protected override void EvaluateScan(ScanQuery s)
		{
			base.EvaluateScan (s);
			if (this.top != Int64.MaxValue)
			{
				this.Result.Insert(0, "SELECT * FROM (");
				this.Result.Append(") WHERE ROWNUM < ");
				this.Result.Append(this.top);
			}
		}


		protected override void EvaluateJoin(JoinOperator j)
		{
			this.Result = new StringBuilder(this.mode == SqlMode.Aggregate ? "" : "SELECT");
			if (this.distinct)
				this.Result.Append(" DISTINCT");

			if (this.mode == SqlMode.Select)
			{
				this.Result.Append(' ');
				string cc = "";

				if (this.columns != null)
				{
					foreach(SelectExpression c in this.columns)
					{
						if (cc != "")
							cc += ",";
						cc += GenerateExpression(c.Expr) + " " + Escape(c.Alias);
					}
				}
				else
				{
					foreach(ExpressionName c in j.Columns)
					{
						if (cc != "")
							cc += ",";
						cc += FullName(c);
					}
				}
				if (cc == "")
					this.Result.Append('1');
				else
					this.Result.Append(cc);
			}

			this.Result.Append(" FROM ");

			CommonInternalDriver driver2 = CreateInternalDriver();

			driver2.Evaluate(j.Source1, j.Correlation);
			this.Result.Append(driver2.Result);
			this.Result.Append(", ");

			string cond1 = Escape(j.Alias) + "." + Escape(j.Column2);
			cond1 += j.IsOuterJoin ? " (+) = " : " = ";
			cond1 += Escape(j.Correlation) + "." + Escape(j.Column1);

			if (driver2.Condition != "")
				cond1 += " AND " + driver2.Condition;

			driver2.Evaluate(j.Source2, j.Alias);
			this.Result.Append(driver2.Result);
			string cond2 = driver2.Condition;
			if (cond2 != "")
				cond1 += " AND " + cond2;	

			this.Result.Append(" WHERE ");
			this.Result.Append(cond1);
			if (this.filter != "") 
			{
				this.Result.Append(" AND ");
				this.Result.Append(this.filter);
			}

			if (sort != null)
			{
				this.Result.Append(" ORDER BY ");
				for (int i = 0; i < sort.Length; i++)
				{
					if (i != 0)
						this.Result.Append(", ");
					this.Result.Append(GenerateExpression(sort[i].Expr));
					if (sort[i].SortType == ScannerToken.Desc)
						this.Result.Append(" DESC");
				}
			}
			this.sort = null;
			if (this.top != Int64.MaxValue)
			{
				this.Result.Insert(0, "SELECT * FROM (");
				this.Result.Append(") WHERE ROWNUM < ");
				this.Result.Append(this.top);
			}
		}

		protected override CommonInternalDriver CreateInternalDriver()
		{
			return new PlSql8InternalDriver(this);
		}

		protected internal class PlSql8InternalDriver: CommonInternalDriver
		{
			public PlSql8InternalDriver(CommonSqlDriver context): base(context)
			{
			}

			protected override void EvaluateJoin(JoinOperator j)
			{
				string res = "";

				Evaluate(j.Source1, j.Correlation);
				res += this.Result;
				res += ", ";

				string cond1 = this.Condition;
				if (cond1 != "")
					cond1 += " AND ";

				cond1 += Escape(j.Alias) + "." + Escape(j.Column2);
				cond1 += j.IsOuterJoin ? " (+) = " : " = ";
				cond1 += Escape(j.Correlation) + "." + Escape(j.Column1);

				this.Evaluate(j.Source2, j.Alias);
				res += this.Result;
				string cond2 = Condition;
				if (cond2 != "")
					cond1 += " AND " + cond2;

				this.Condition = cond1;

				this.Result = res;
			}
		}

		protected override string GenerateExpression(ExpressionNode en)
		{
#if EscapeNames
			return EscapeCondition(PlsqlExpressionStringizer.Evaluate(this, en));
#else
			return base.GenerateExpression(en);
#endif
		}

		class PlsqlExpressionStringizer: CommonExpressionStringizer
		{
			public static string Evaluate(PlSql8Driver driver, ExpressionNode n)
			{
				PlsqlExpressionStringizer es = new PlsqlExpressionStringizer(driver);
				n.Accept(es);
				return es.Result.ToString();
			}

			protected PlsqlExpressionStringizer(PlSql8Driver driver)
				: base(driver)
			{
			}

			public override void VisitName(ExpressionName n)
			{
				if (n.Qualifier != "") 
				{
					this.Result.Append("\"" + n.Qualifier + "\"");
					this.Result.Append('.');
					this.Result.Append(n.Value);
					return;
				}
				this.Result.Append(n.Value);
			}
		}
	}
}
